﻿/*
Copyright © 2011 François Karman

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

See <license.txt> for the complete license of the software.
*/

using System;
using System.IO;
using Simple.Storage.Serializer;

namespace Simple.Storage.Api
{
    /// <summary>
    /// Implements the <see cref="ISerializer"/> contract for the links between class definition
    /// as part of the default serialization strategy.
    /// </summary>
    /// <seealso cref="IInitializer.CreateLinkSerializer"/>
    public sealed class LinkSerializer : AbstractMonoTypeSerializer
    {
        /// <summary>
        /// The associated data source - used to save/load data.
        /// </summary>
        private readonly DataSource datasource;

        /// <summary>
        /// The associated class definition.
        /// </summary>
        private readonly IClassDefinition definition;

        /// <summary>
        /// The serializer used for the key of the associated class definition.
        /// </summary>
        private readonly ISerializer parent;

        /// <summary>
        /// Initializes a new instance of the LinkSerializer class.
        /// </summary>
        /// <param name="datasource">
        /// The associated data source - used to save/load data.
        /// </param>
        /// <param name="definition">
        /// The associated class definition.
        /// </param>
        /// <param name="parent">
        /// The serializer used for the key of the associated class definition.
        /// </param>
        public LinkSerializer(IDataSource datasource, IClassDefinition definition, ISerializer parent)
        {
            if (datasource == null)
            {
                throw new ArgumentNullException("datasource");
            }
            else if (definition == null)
            {
                throw new ArgumentNullException("definition");
            }
            else if (parent == null)
            {
                throw new ArgumentNullException("parent");
            }

            this.datasource = datasource as DataSource;
            if (this.datasource == null)
            {
                string message = string.Format(
                    Messages.Culture,
                    Messages.LinkSerializerArgument,
                    datasource.GetType().FullName);
                throw new ArgumentException(message, "datasource");
            }

            this.definition = definition;
            this.parent = parent;
        }

        /// <summary>
        /// Gets the type of elements managed by the serializer.
        /// </summary>
        protected override Type AssociatedType
        {
            get { return this.definition.Type; }
        }

        /// <summary>
        /// Implements the serialization logic for the type associated with this serializer.
        /// </summary>
        /// <param name="writer">
        /// The writer - guaranty to be not <c>null</c>.
        /// </param>
        /// <param name="value">
        /// The value to serialize - guaranty to be not <c>null</c> and associated with
        /// the current serializer.
        /// </param>
        protected override void InternalSerialize(BinaryWriter writer, object value)
        {
            writer.Write(value != null);
            if (value != null)
            {
                object key = this.definition.RetrieveKey(value);
                this.parent.Serialize(writer, this.definition.KeyType, key);

                this.datasource.Save(this.definition.Type, value);
            }
        }

        /// <summary>
        /// Implements the deserialization logic for the type associated with this serializer.
        /// </summary>
        /// <param name="reader">
        /// The reader - guaranty to be not <c>null</c>.
        /// </param>
        /// <returns>The deserialized value.</returns>
        protected override object InternalDeserialize(BinaryReader reader)
        {
            if (!reader.ReadBoolean())
            {
                return null;
            }

            object key = this.parent.Deserialize(reader, this.definition.KeyType);

            return this.datasource.Load(this.definition.Type, key);
        }
    }
}
